import tkinter as tk
from tkinter import ttk, scrolledtext
import socket
import subprocess
import csv
import threading
from concurrent.futures import ThreadPoolExecutor
import time
#v2.1

def scan_port(ip, port, timeout=1):
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.settimeout(timeout)
            result = s.connect_ex((ip, port))
            if result == 0:
                return port
    except Exception as e:
        pass
    return None


def scan_ports(ip, start_port=1, end_port=65535, max_threads=10000):
    open_ports = []
    with ThreadPoolExecutor(max_workers=max_threads) as executor:
        futures = [executor.submit(scan_port, ip, port) for port in range(start_port, end_port + 1)]
        for future in futures:
            result = future.result()
            if result:
                open_ports.append(result)
    return open_ports


def ping_host(ip):
    try:
        # Windows系统使用-c，Linux/Mac使用-n
        param = "-n" if os.name == "nt" else "-c"
        result = subprocess.run(["ping", param, "1", ip],
                                capture_output=True, text=True, timeout=5)
        return result.returncode == 0
    except:
        return False


def telnet_port(ip, port, timeout=3):
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.settimeout(timeout)
            result = s.connect_ex((ip, port))
            return result == 0
    except:
        return False




class PortScannerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("网络扫描工具")
        self.root.geometry("800x600")

        # 创建界面元素
        self.create_widgets()

    def create_widgets(self):
        # IP地址输入框
        tk.Label(self.root, text="IP地址 (多个IP用逗号分隔):").pack(pady=5)
        self.ip_entry = tk.Entry(self.root, width=40)
        self.ip_entry.pack(pady=5)
        self.ip_entry.insert(0, "172.25.61.76")

        # 端口范围输入
        port_frame = tk.Frame(self.root)
        port_frame.pack(pady=5)
        tk.Label(port_frame, text="端口范围:").pack(side=tk.LEFT)
        self.start_port_entry = tk.Entry(port_frame, width=10)
        self.start_port_entry.pack(side=tk.LEFT, padx=5)
        self.start_port_entry.insert(0, "1")
        tk.Label(port_frame, text="-").pack(side=tk.LEFT)
        self.end_port_entry = tk.Entry(port_frame, width=10)
        self.end_port_entry.pack(side=tk.LEFT, padx=5)
        self.end_port_entry.insert(0, "65535")

        # 按钮框架
        button_frame = tk.Frame(self.root)
        button_frame.pack(pady=10)

        self.ping_button = tk.Button(button_frame, text="Ping测试", command=self.start_ping)
        self.ping_button.pack(side=tk.LEFT, padx=5)

        self.telnet_button = tk.Button(button_frame, text="Telnet测试", command=self.start_telnet)
        self.telnet_button.pack(side=tk.LEFT, padx=5)

        self.scan_button = tk.Button(button_frame, text="端口扫描", command=self.start_scan)
        self.scan_button.pack(side=tk.LEFT, padx=5)

        self.export_button = tk.Button(button_frame, text="导出CSV", command=self.export_to_csv)
        self.export_button.pack(side=tk.LEFT, padx=5)

        # 转置导出按钮
        self.transpose_button = tk.Button(button_frame, text="转置导出", command=self.transpose_from_csv)
        self.transpose_button.pack(side=tk.LEFT, padx=5)

        # CSV文件路径输入框
        tk.Label(self.root, text="CSV文件路径:").pack(pady=5)
        self.csv_file_entry = tk.Entry(self.root, width=40)
        self.csv_file_entry.pack(pady=5)
        self.csv_file_entry.insert(0, "scan_results_1760323312.csv")

        # 输出框
        tk.Label(self.root, text="扫描结果:").pack(pady=5)
        self.output_text = scrolledtext.ScrolledText(self.root, width=80, height=25)
        self.output_text.pack(pady=5, padx=10, fill=tk.BOTH, expand=True)

        # 结果存储
        self.results = []

    def start_ping(self):
        self.output_text.delete(1.0, tk.END)
        self.results = []
        ips = self.get_ips()
        if not ips:
            self.output_text.insert(tk.END, "请输入有效的IP地址\n")
            return

        # 在新线程中执行，避免界面冻结
        threading.Thread(target=self.ping_ips, args=(ips,), daemon=True).start()

    def ping_ips(self, ips):
        self.output_text.insert(tk.END, f"开始Ping测试 {len(ips)} 个IP地址...\n")
        for ip in ips:
            self.output_text.insert(tk.END, f"Ping {ip}...\n")
            self.root.update()

            is_alive = ping_host(ip)
            status = "在线" if is_alive else "离线"
            result = {
                'ip': ip,
                'type': 'ping',
                'status': status,
                'details': ''
            }
            self.results.append(result)
            self.output_text.insert(tk.END, f"  {ip}: {status}\n")

        self.output_text.insert(tk.END, "Ping测试完成\n")

    def start_telnet(self):
        self.output_text.delete(1.0, tk.END)
        self.results = []
        ips = self.get_ips()
        if not ips:
            self.output_text.insert(tk.END, "请输入有效的IP地址\n")
            return

        try:
            start_port = int(self.start_port_entry.get())
            end_port = int(self.end_port_entry.get())
        except ValueError:
            self.output_text.insert(tk.END, "请输入有效的端口范围\n")
            return

        # 在新线程中执行
        threading.Thread(target=self.telnet_ports, args=(ips, start_port, end_port), daemon=True).start()

    def telnet_ports(self, ips, start_port, end_port):
        self.output_text.insert(tk.END, f"开始Telnet测试 {len(ips)} 个IP地址，端口范围 {start_port}-{end_port}...\n")
        for ip in ips:
            self.output_text.insert(tk.END, f"测试 {ip}...\n")
            self.root.update()

            # 测试单个端口还是端口范围
            if start_port == end_port:
                is_open = telnet_port(ip, start_port)
                status = "开放" if is_open else "关闭"
                result = {
                    'ip': ip,
                    'type': 'telnet',
                    'status': status,
                    'details': f'端口 {start_port}'
                }
                self.results.append(result)
                self.output_text.insert(tk.END, f"  {ip}:{start_port} {status}\n")
            else:
                # 使用线程池并发测试端口，最大并发数设为100
                common_ports = list(range(start_port, end_port + 1))
                open_ports = []

                # 使用ThreadPoolExecutor提高并发数到100
                with ThreadPoolExecutor(max_workers=1000) as executor:
                    # 提交所有端口测试任务
                    future_to_port = {executor.submit(telnet_port, ip, port): port for port in common_ports}

                    # 收集结果
                    for future in future_to_port:
                        port = future_to_port[future]
                        try:
                            is_open = future.result()
                            if is_open:
                                open_ports.append(str(port))
                            self.output_text.insert(tk.END, f"    端口 {port}: {'开放' if is_open else '关闭'}\n")
                            self.root.update()  # 保持界面响应
                        except Exception as e:
                            self.output_text.insert(tk.END, f"    端口 {port}: 测试失败 {str(e)}\n")

                status = "部分开放" if open_ports else "全部关闭"
                result = {
                    'ip': ip,
                    'type': 'telnet',
                    'status': status,
                    'details': f'开放端口: {", ".join(open_ports)}' if open_ports else '无开放端口'
                }
                self.results.append(result)

        self.output_text.insert(tk.END, "Telnet测试完成\n")

    def start_scan(self):
        self.output_text.delete(1.0, tk.END)
        self.results = []
        ips = self.get_ips()
        if not ips:
            self.output_text.insert(tk.END, "请输入有效的IP地址\n")
            return

        try:
            start_port = int(self.start_port_entry.get())
            end_port = int(self.end_port_entry.get())
        except ValueError:
            self.output_text.insert(tk.END, "请输入有效的端口范围\n")
            return

        # 在新线程中执行
        threading.Thread(target=self.scan_ips, args=(ips, start_port, end_port), daemon=True).start()

    def scan_ips(self, ips, start_port, end_port):
        self.output_text.insert(tk.END, f"开始端口扫描 {len(ips)} 个IP地址，端口范围 {start_port}-{end_port}...\n")
        for ip in ips:
            self.output_text.insert(tk.END, f"扫描 {ip}...\n")
            self.root.update()

            try:
                open_ports = scan_ports(ip, start_port, end_port)
                if open_ports:
                    ports_str = ", ".join(map(str, sorted(open_ports)))
                    self.output_text.insert(tk.END, f"  开放端口: {ports_str}\n")
                    result = {
                        'ip': ip,
                        'type': 'scan',
                        'status': '成功',
                        'details': ports_str
                    }
                else:
                    self.output_text.insert(tk.END, f"  未发现开放端口\n")
                    result = {
                        'ip': ip,
                        'type': 'scan',
                        'status': '无开放端口',
                        'details': ''
                    }
                self.results.append(result)
            except Exception as e:
                self.output_text.insert(tk.END, f"  扫描失败: {str(e)}\n")
                result = {
                    'ip': ip,
                    'type': 'scan',
                    'status': '失败',
                    'details': str(e)
                }
                self.results.append(result)

        self.output_text.insert(tk.END, "端口扫描完成\n")

    def transpose_from_csv(self):
        try:
            # 从输入框获取文件路径
            input_filename = self.csv_file_entry.get().strip()

            if not input_filename:
                self.output_text.insert(tk.END, "请输入CSV文件路径\n")
                return

            # 检查文件是否存在
            import os
            if not os.path.exists(input_filename):
                self.output_text.insert(tk.END, f"文件 {input_filename} 不存在\n")
                return

            # 读取CSV数据
            data = []
            with open(input_filename, 'r', encoding='utf-8-sig') as csvfile:
                reader = csv.DictReader(csvfile)
                for row in reader:
                    data.append(row)

            if not data:
                self.output_text.insert(tk.END, "CSV文件中没有数据\n")
                return

            # 过滤出scan类型的记录并有details数据的记录
            scan_records = [r for r in data if r['type'] == 'scan' and r['details']]

            if not scan_records:
                self.output_text.insert(tk.END, "没有找到scan类型的记录\n")
                return

            # 获取所有唯一端口
            all_ports = set()
            for record in scan_records:
                if record['details']:
                    ports = [p.strip() for p in record['details'].split(',')]
                    all_ports.update(ports)

            all_ports = sorted(list(all_ports), key=lambda x: int(x))

            # 构建转置后的数据结构，使用IP作为列标题
            transposed_header = ['Port'] + [record['ip'] for record in scan_records]
            transposed_data = []

            # 为每个端口创建一行数据
            for port in all_ports:
                row = [port]
                for record in scan_records:
                    port_list = [p.strip() for p in record['details'].split(',')] if record['details'] else []
                    row.append('1' if port in port_list else '0')
                transposed_data.append(row)

            # 写入转置后的CSV文件
            output_filename = "zhunzhi.csv"
            with open(output_filename, 'w', newline='', encoding='utf-8-sig') as csvfile:
                writer = csv.writer(csvfile)

                # 写入表头
                writer.writerow(transposed_header)

                # 写入数据
                for row in transposed_data:
                    writer.writerow(row)

            self.output_text.insert(tk.END, f"转置完成，结果已保存到 {output_filename}\n")

        except Exception as e:
            self.output_text.insert(tk.END, f"转置过程出错: {str(e)}\n")


    def get_ips(self):
        ip_text = self.ip_entry.get().strip()
        if not ip_text:
            return []

        ips = [ip.strip() for ip in ip_text.split(",") if ip.strip()]
        return ips

    def export_to_csv(self):
        if not self.results:
            self.output_text.insert(tk.END, "没有数据可导出\n")
            return

        try:
            filename = f"scan_results_{int(time.time())}.csv"
            with open(filename, 'w', newline='', encoding='utf-8-sig') as csvfile:
                fieldnames = ['ip', 'type', 'status', 'details']
                writer = csv.DictWriter(csvfile, fieldnames=fieldnames)

                writer.writeheader()
                for result in self.results:
                    writer.writerow(result)

            self.output_text.insert(tk.END, f"结果已导出到 {filename}\n")
        except Exception as e:
            self.output_text.insert(tk.END, f"导出失败: {str(e)}\n")




if __name__ == "__main__":
    import os

    root = tk.Tk()
    app = PortScannerApp(root)
    root.mainloop()
